package com.ltu.util.redis;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

import javax.annotation.Resource;

import org.apache.commons.collections.map.HashedMap;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ltu.util.exception.ApiException;

/**  
 * @author: 若尘  
 * @Description: 基于spring-boot-starter-data-redis基础上封装的redis工具
 * @date 2019年4月16日 上午10:16:18
 * @version V1.0  
 */
@Component
public class RedistUtil {
	
	@Resource
    private RedisTemplate<String,Object> redisTemplate;
	
	private final static String LOCKSCRIPT="if redis.call('setnx',KEYS[1],KEYS[2])==1 then  redis.call('pexpire',KEYS[1],KEYS[3]) return 1 else 0 ";
	private final static String UNLOCKSCRIPT="if redis.call('get',KEYS[1]) == KEYS[2]  then return redis.call('del',KEYS[1])  else 0 ";
	private static  AtomicLong   TIMEOFFSET=new AtomicLong (0l);
	public static  AtomicLong   nxFlag=new AtomicLong (0l);
	
	public static long getTimeOffset(){
		if(TIMEOFFSET.intValue()>5000)
			TIMEOFFSET.set(0l);
		return TIMEOFFSET.getAndIncrement();
	}
	/**
	* @Description: TODO(获取时间片延长时间，时间片=delayed+(0~5000))
	* @param delayed 时间片的值比如2000，表示这个时间片将2~7秒后其他线程可以重入锁
	* @return long    返回类型
	 */
	public static long getTimeSlice(long delayed){
		if(TIMEOFFSET.intValue()>5000)
			TIMEOFFSET.set(0l);
		long timeSlice= delayed+TIMEOFFSET.getAndIncrement()+System.currentTimeMillis();
		return  timeSlice;
	}
	
    public Object getValue(String key){
   	 try {
		if(redisTemplate.hasKey(key)){
			ValueOperations<String, Object> options=    redisTemplate.opsForValue();
//			 byte[] value = options.get(key);			 
//			 if(value!=null && value.length>0){
//					ByteArrayInputStream  bais=new ByteArrayInputStream(value);
//		        	ObjectInputStream ois=new ObjectInputStream(bais);
//		        	return	  ois.readObject();
//			 }
			return  options.get(key);
		}
		   
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
   	 return null;
   }
    
    /**
     * @date 2019年4月17日
     * @Description 写入新值返回旧值
     * @param key
     * @param value
     * @return
     */
    public Object getSetValue(String key,Object value){
      	 try {
   		
   			ValueOperations<String, Object> options=    redisTemplate.opsForValue();

   			Object oldValue = options.getAndSet(key, value);

   			return oldValue;
   		   
   	} catch (Exception e) {
   		// TODO Auto-generated catch block
   		e.printStackTrace();
   	}
      	 return null;
   }
    public Object getSetValue(String key,Object value,Long timeOut){
     	 try {
  		
  			ValueOperations<String, Object> options=    redisTemplate.opsForValue();

  			Object oldValue = options.getAndSet(key, value);

  			redisTemplate.expire(key, timeOut,  TimeUnit.SECONDS);
  			
  			return oldValue;
  		   
  	} catch (Exception e) {
  		// TODO Auto-generated catch block
  		e.printStackTrace();
  	}
     	 return null;
  }
    
    /**
     * @Description 写入缓存
     * @param key
     * @param obj
     * @param time 过期时间 默认以TimeUnit.SECONDS单位
     * void
     */
    public  void  setValue(String key,Object obj,Long time){
   	 try {
   		 if(key==null ||  key.length()==0 || obj==null ||  obj.toString().length()==0 ){
   			 throw new ApiException("50000","缓存操作的key和value不能为null或是空字符");
   		 }
//   		ByteArrayOutputStream  baos=new ByteArrayOutputStream(128);
//   		ObjectOutputStream ois=new ObjectOutputStream(baos);
//   		ois.writeObject(obj);
//   	 byte[] value =	baos.toByteArray();
		 System.out.print("缓存的数据是------"+obj+"\n");
		ValueOperations<String, Object> options=    redisTemplate.opsForValue();
		if(time==null){
			options.set(key, obj,3600l, TimeUnit.SECONDS);
		}else{
			options.set(key, obj,time, TimeUnit.SECONDS);
		}
		
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}

  }
    /**
     * @Description 写入缓存
     * @param key
     * @param obj
     * @param time 过期时间 默认以TimeUnit.SECONDS单位
     * void
     */
    public  void  setValue(String key,Object obj,Long time,TimeUnit unit){
    	try {
    		if(key==null ||  key.length()==0 || obj==null ||  obj.toString().length()==0 ){
    			throw new ApiException("50000","缓存操作的key和value不能为null或是空字符");
    		}
//   		ByteArrayOutputStream  baos=new ByteArrayOutputStream(128);
//   		ObjectOutputStream ois=new ObjectOutputStream(baos);
//   		ois.writeObject(obj);
//   	 byte[] value =	baos.toByteArray();
    		System.out.print("缓存的数据是------"+obj+"\n");
    		ValueOperations<String, Object> options=    redisTemplate.opsForValue();
    		if(time==null){
    			options.set(key, obj,3600l, TimeUnit.SECONDS);
    		}else{
    			options.set(key, obj,time, unit);
    		}
    		
    	} catch (Exception e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    	
    }
   
    
    public  void  setValue(String key,Object obj){
      	 try {
      		 if(key==null ||  key.length()==0 || obj==null ||  obj.toString().length()==0 ){
      			 throw new ApiException("50000","缓存操作的key和value不能为null或是空字符");
      		 }
//      		ByteArrayOutputStream  baos=new ByteArrayOutputStream(128);
//      		ObjectOutputStream ois=new ObjectOutputStream(baos);
//      		ois.writeObject(obj);
//      	 byte[] value =	baos.toByteArray();
      		 ValueOperations<String, Object> options=    redisTemplate.opsForValue();
      		options.set(key, obj);
   			options.set(key, obj,3600l, TimeUnit.SECONDS);
	   	} catch (Exception e) {
	   		// TODO Auto-generated catch block
	   		e.printStackTrace();
	   	}

     }
    
    public  void  setValueNoExpiret(String key,Object obj){
     	 try {
     		 if(key==null ||  key.length()==0 || obj==null ||  obj.toString().length()==0 ){
     			 throw new ApiException("50000","缓存操作的key和value不能为null或是空字符");
     		 }
//     		ByteArrayOutputStream  baos=new ByteArrayOutputStream(128);
//     		ObjectOutputStream ois=new ObjectOutputStream(baos);
//     		ois.writeObject(obj);
//     	 byte[] value =	baos.toByteArray();
     		 ValueOperations<String, Object> options=    redisTemplate.opsForValue();
     		options.set(key, obj);
	   	} catch (Exception e) {
	   		// TODO Auto-generated catch block
	   		e.printStackTrace();
	   	}

    }
    
    public boolean hasKey(String key){
    	 
    	return redisTemplate.hasKey(key);
    }
    
    
	public void expireAt(String key, final Date date) {
		 redisTemplate.expireAt(key, date);
		
	}
  
    
    /**
	 * @date 2019年4月17日
	 * @Description 为指定key的value增量或减量
     * @param key
     * @param increment 增量>0或减量<0
	 * @param timeOut 过期时间以秒为单位
	 * @return 返回key增量后的value值
	 */
    public long incrementBy(String key,long increment,long timeOut){
	    long value=	redisTemplate.opsForValue().increment(key, increment);
	    
	    redisTemplate.expire(key, timeOut, TimeUnit.SECONDS);

    	return value;
    }
    
    public Long incrementBy(String key,long increment){
    	Long value = null;
    	if(this.containsKey(key))
    		 value=	redisTemplate.opsForValue().increment(key, increment);
    	return value;
    }
    
    
    public static final String LOCK_PREFIX = "lock:";
    //过期时间
    public static final int LOCK_EXPIRE = 1000; // ms

    /**
     * 若尘： 2019-08-24优化
     * 分布式锁
     * @param key key值
     * @param expire 锁的过期间 秒为单位。最好不要把外节点的操作置于锁内。假设锁内含有外节点业务则应考虑在内
     * @return 是否获取到 true拥有锁 
     */
    public boolean lock(String key,long expire){
    	   String lock = LOCK_PREFIX + key;
           return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
        	   // 时间片   CPU拥有该锁的线程有多长的处理时间  
               long expireAt = System.currentTimeMillis() + LOCK_EXPIRE;  
               	//setNX具有原子操作 将当前业务的key 和业务的一个时间片为值 作为锁对象。 当同一个key和同一段时间片同时写入redis中只有一个线程可以写入成功
               Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
               if (acquire) {   
            	   connection.expire(lock.getBytes(), expire);//如果出现宕机没有设置过期时间也没关系，其他线程会根据该锁中的value判断是否过期，以此获取锁
            	   return true;
               } else {
                   byte[] value = connection.get(lock.getBytes());  
                   if (Objects.nonNull(value) ) {
                	   String  expireTime=new String(value); 
                	//  如果当前服务器宕机未能执行 connection.expire(lock.getBytes(), expire)其他服务器实例依旧可以根据这个锁中的时间片判断是否过期
                	   if(Long.parseLong(expireTime) < System.currentTimeMillis()){
                		   expireAt = System.currentTimeMillis() + LOCK_EXPIRE ; //重写设置时间片
                		   //getSet方法是具有原子操作的
                		   byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(expireAt).getBytes());
                           if (oldValue!=null && expireTime.equals( new String(oldValue))) {  //只有一个线程拿到的时间会和上个锁的时间相等
                               // 当出现异常的时候该锁可以通过过期自处理
                               connection.expire(lock.getBytes(), expire);
                               return true;
                           }
                	   }
                   }
               }
               return false;
           });
    }
    
    
    /**
     * 
    * @Description: TODO(timeSlice所内业务执行需要的大概时间通常1s就够了timeSlice = System.currentTimeMillis() + 1000
    * @param  key   锁key
    * @param  expire  锁的过期时间秒为单位
    * @param  timeSlice 时间片,单位毫秒(要评估好锁内业务的执行时间通常500ms就足够了,解锁的时候需要根据这个值解锁，只有等于的情况下才做解锁操作)   
    * @return boolean    返回类型
     */
    public Boolean lock(String key,long expire,long timeSlice){
    	
 	    String lock = LOCK_PREFIX + key;
        return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
        		//基于原子操作下获取的时间片
        	long timeSlice2=  RedistUtil.getTimeSlice(timeSlice);
            	//setNX具有原子操作 将当前业务的key 和业务的一个时间片为值 作为锁对象。 当同一个key和同一段时间片同时写入redis中只有一个线程可以写入成功
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(timeSlice2).getBytes());
            if (acquire) {   
            	//如果出现宕机没有设置过期时间也没关系，其他线程会根据该锁中的value判断是否过期，以此获取锁
         	   connection.expire(lock.getBytes(), expire);
//         	   RedistUtil.nxFlag.incrementAndGet(); //5000并发请求下只有1个线程持有锁，因此setNX命令是具备原子操作的
         	 
         	   return true;
            } else {
                byte[] value = connection.get(lock.getBytes());  
                if (Objects.nonNull(value) ) {
             	   String  expireTime=new String(value); 
             	   //如果当前服务器宕机未能执行 connection.expire(lock.getBytes(), expire)其他服务器实例依旧可以根据这个锁中的时间片判断是否过期
             	   long currentTime=System.currentTimeMillis();
             	   long redisTimeSlice=Long.parseLong(expireTime);
             	   if( redisTimeSlice <currentTime ){
             		   	//基于
             		     timeSlice2 =  RedistUtil.getTimeSlice(timeSlice);
             		   //10000并发压测getSet方法是具有原子操作的
             		   byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(timeSlice2).getBytes());
             		   String  oldTime= new String(oldValue); 
             		   if (oldValue!=null && expireTime.equals(oldTime)) {  //只有一个线程拿到的时间会和上个锁的时间相等
                            // 当出现异常的时候该锁可以通过过期自处理
							//StringBuilder str = new StringBuilder(); str.append("\n 旧的时间片："); str.append(expireTime); str.append(",通过getset命令后的时间片："); str.append(oldTime); str.append(",是否相等："); str.append(expireTime.equals(oldTime)); str.append("\n 当前线程制" + Thread.currentThread().getName() + "的时间片："); str.append(timeSlice2); str.append("与旧的时间片:"); str.append(expireTime); str.append("相差："); str.append(timeSlice2 - redisTimeSlice); str.append("与当前时间:"); str.append(currentTime); str.append("相差："); str.append(timeSlice2 - currentTime); System.out.println(str);
                            connection.expire(lock.getBytes(), expire);
                            return true;
                        }
             	   }
                }
            }
            return   false;
        });
 }
    
         
    /**
     * 删除锁
     * @param key
     */
    public void deleteLock(String key) {
        redisTemplate.delete(LOCK_PREFIX + key);
    }

	/**
	 * @date 2019年4月18日
	 * @author 若尘
	 * @Description
	 * void
	 */
	public void removeKey(String key) {
		redisTemplate.delete( key);
	}
	/**
	 * @date 2019年4月18日
	 * @author 若尘
	 * @Description
	 * void
	 */
	public void removeKeys(List<String> keys) {
		redisTemplate.delete( keys);
	}
	public boolean containsKey(String key){
		return redisTemplate.hasKey(key);
	}
	
	 public void putOneHashMap(String key,String hashKey,Object val) {
	    	redisTemplate.opsForHash().put(key, hashKey,val);
	    }
	    public void putAllHashMap(String key,Map<String,Object> mps) {
	    	redisTemplate.opsForHash().putAll(key, mps);
	    }
	    public Object  getOneHashMap(String key,String hashKey) {
	    	return redisTemplate.opsForHash().get(key, hashKey);
	    }
	    public Map<Object, Object>  getAllHashMap(String key) {
	    	return redisTemplate.opsForHash().entries(key);
	    }
	    
	    /**
	     * 根据key前缀获取所有key
	     * @param prefix key的前缀
	     * @return
	     */
	    public Set<String>  getKeysByPrefix(String prefix){
	    	  Set<String> keys = redisTemplate.keys(prefix);
	    	  return keys;
	    }
	    
	    /**
	     * 根据key前缀获取所有key的value
	     * @param prefix key的前缀
	     * @return
	     */
	    public List<Object>  getValuesByPrefix(String prefix){
	    	  Set<String> keys =getKeysByPrefix(prefix);
	    	  List<Object> values =  	redisTemplate.opsForValue().multiGet(keys);
	    	  return values;
	    }

	
	
	  /**
     * 使用scan命令 查询某些前缀的key
     * @param key
     * @return
     */
    public Set<String> scan(String key){
        Set<String> execute = this.redisTemplate.execute(new RedisCallback<Set<String>>() {

            @Override
            public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {

                Set<String> binaryKeys = new HashSet<>();

                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(1000).build());
                while (cursor.hasNext()) {
                    binaryKeys.add(new String(cursor.next()));
                }
                return binaryKeys;
            }
        });
        return execute;
    }
	


    /**
     * 使用scan命令 查询某些前缀的key 有多少个
     * 用来获取当前session数量,也就是在线用户
     * @param key
     * @return
     */
    public Long scanSize(String key){
        long dbSize = this.redisTemplate.execute(new RedisCallback<Long>() {

            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                long count = 0L;
                Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(1000).build());
                while (cursor.hasNext()) {
                    cursor.next();
                    count++;
                }
                return count;
            }
        });
        return dbSize;
    }
    
	public void refreshExpireTime(String key, long expiretime) {
		// TODO Auto-generated method stub
		redisTemplate.expire(key, expiretime, TimeUnit.SECONDS);
		
		
	}
	/**
	 * 字符串：取对象
	 * @param key
	 * @param targetClass：实体对象
	 * @return
	 */
	public <T> T getObject(final String key, Class<T> targetClass) {

		Object object = getValue(key);
		if (object == null) {
			return null;
		}
		ObjectMapper objectMapper = new ObjectMapper();
		return  objectMapper.convertValue(object, targetClass);
	}
	
	/**
	 * 获取列表中指定start至end数据，下标从0开始
	 * @param key
	 * @param start
	 * @param end
	 * @return
	 */
	public  List<Object> getList(String key,long start,long end) {
	 
		List<Object> values=this.redisTemplate.opsForList().range(key, start, end);
		
		return  values;
	}
	/**
	 *裁剪列表 从start至end数据，下标从0开始
	 * @param key
	 * @param start
	 * @param end
	 * @return
	 */
	public  void trim(String key,long start,long end) {
	 
		this.redisTemplate.opsForList().trim(key, start, end);
		
	}
	
	
    
}
